/*
 *  pci.c
 *  xcode
 *
 *  Created by Emiel Goor,van on 24-01-08.
 *  Copyright 2008 __MyCompanyName__. All rights reserved.
 *
 */

#include "pci.h"
#include "pci_info.h"

// modified code from ominOS (Sourceforge)
// used with permission

// types

// bios header
typedef struct tagBIOSHEADER
{
	unsigned int	signature;
	unsigned int	entry;
	unsigned char	version;
	unsigned char	length;
	unsigned char	checksum;
	unsigned char	reserved[5];
} biosHeader_t;

union bios32_t
{
	biosHeader_t header;
	unsigned char c[16];
};

// config command
struct cfg_cmd
{
	uint8_t reg:8;
	uint8_t func:3;
	uint8_t dev:5;
	uint8_t bus:8;
	uint8_t reserved:7;
	uint8_t enable:1;
};

union cmd
{
	struct cfg_cmd cc;
	uint32_t ci;
};

static struct
{
	uint32_t address;
	uint32_t segment;
} bios32Indirect = { 0, 0x08 };

static struct
{
	uint32_t address;
	uint32_t segment;
} pciIndirect = { 0, 0x08 };

// array of pci devices
static pciDev_t *pciDevices[256];

// number of pci devices
static int pciCount = 0;

// bios signature
#define BIOS32_SIGNATURE ( '_' | ('3' << 8) | ('2' << 16) | ('_' << 24) )

// gets a pci device vendor string
char *pciGetVendorStr(uint16_t vendor)
{
	int i;
	static char ven[] = "UNKNOWN";
	for(i=0;i<PCI_VENTABLE_LEN;i++)
	{
		 if(PciVenTable[i].VenId == vendor)
			return PciVenTable[i].VenShort;
	}
	return ven;
}

// gets a pci device string
char *pciGetDevStr(uint16_t ven, uint16_t dev)
{
	int i;
	static char devstr[] = "UNKNOWN";
	for(i=0; i<PCI_DEVTABLE_LEN;i++)
	{
		if(PciDevTable[i].VenId == ven && PciDevTable[i].DevId == dev)
			return PciDevTable[i].ChipDesc;
	}
	
	return devstr;
}

// gets a device by its id
pciDev_t *pciGetDeviceById(uint16_t vendor, uint32_t device, int index)
{
	int i = 0;
	int c = 0;
	
	while(i < pciCount)
	{
		if(pciDevices[i]->vendor_id == vendor && 
			pciDevices[i]->device_id == device)
		{
			c++;
			if(c == index)
				return pciDevices[i];
		}
		i++;
	}
	return (pciDev_t*) NULL;
}

// queries bios32
uint32_t bios32Query(union bios32_t *bios32_s, uint32_t service)
{
	uint8_t returnCode;
	uint32_t address;
	uint32_t length;
	uint32_t entry;

	__asm__("lcall *(%%edi); cld"
				: "=a" (returnCode),
				  "=b" (address),
				  "=c" (length), 
				  "=d" (entry)
				: "0" (service),
				  "1" (0),
				  "D" (&bios32Indirect));

	switch(returnCode)
	{
		case 0x0:
			return address + entry;
		case 0x80:
			printf("PCI: BIOS not present.\n");
			return 0;
		case 0x81:
			printf("PCI: Unimplemented Function\n");
			return 0;
		default:
			printf("PCI: Bug\n");
			return 0;
	}
}

// finds the bios32 service directory
static biosHeader_t *findBios32ServiceDirectory(void)
{
	uint8_t *start = (uint8_t *)0xe0000;
	biosHeader_t *bios32;
	
	while(start < (uint8_t *)0x100000)
	{
		bios32 = (biosHeader_t *)start;
		if(bios32->signature == BIOS32_SIGNATURE)
		{
			uint8_t sum = 0;
			while(start - (uint8_t *)bios32 < bios32->length * 16)
				sum += *start++;

			if(sum == 0) 
			{
				printf("PCI: BIOS32 Service Directory found, entry: 0x%x\n", bios32->entry);
				bios32Indirect.address = bios32->entry;
				return bios32;
			}
			start = (uint8_t *)bios32;
		}
		start += 16;
	}

	printf("PCI: BIOS32 Service Directory not detected\n");
	return 0;
}

// reads a config dword
uint32_t pciReadConfigDword(int bus, int dev, int func, int reg)
{
	uint16_t base;
	union cmd cmd;

	cmd.cc.enable = 1;
	cmd.cc.reserved = 0;
	cmd.cc.bus = bus & 0xff;
	cmd.cc.dev = dev & 0x1f;
	cmd.cc.func = func & 0x07;
	cmd.cc.reg = reg & 0xfc;


	outportd(0xcf8, cmd.ci);
	base = 0xcfc + (reg & 0x03);
	
	return inportd(base);
}

// reads a config word
uint16_t pciReadConfigWord(int bus, int dev, int func, int reg)
{
	uint16_t base;
	union cmd cmd;

	cmd.cc.enable = 1;
	cmd.cc.reserved = 0;
	cmd.cc.bus = bus & 0xFF;
	cmd.cc.dev = dev & 0x1F;
	cmd.cc.func = func & 0x7;
	cmd.cc.reg = reg & 0xFC;

	outportd(0xCF8, cmd.ci);
	base = 0xCFC + (reg & 0x03);

	return inw(base);
}

// reads a config byte
uint8_t pciReadConfigByte(int bus, int dev, int func, int reg)
{
    uint16_t base;
    union cmd cmd;

	cmd.cc.enable = 1;
    cmd.cc.reserved = 0;
    cmd.cc.bus = bus & 0xFF;
    cmd.cc.dev = dev & 0x1F;
    cmd.cc.func = func & 0x7;
    cmd.cc.reg = reg & 0xFC;

    outportd(0xCF8, cmd.ci);
    base = 0xCFC + (reg & 0x03);

    return inb(base);	
}

// writes a config dword
void pciWriteConfigDword(int bus, int dev, int func, int reg, uint32_t data)
{
    uint16_t base;
    union cmd cmd;

	cmd.ci = 0;
	cmd.cc.enable = 1;
    cmd.cc.reserved = 0;
    cmd.cc.bus = bus;
    cmd.cc.dev = dev;
    cmd.cc.func = func;
    cmd.cc.reg = reg & 0xFC;
   
	base = 0xcfc + (reg & 0x03); 
    outportd(0xCF8, cmd.ci);
	outportd(base, data);
}

// writes a config word
void pciWriteConfigWord(int bus, int dev, int func, int reg, uint16_t data)
{
    uint16_t base;
    union cmd cmd;

	cmd.ci = 0;
	cmd.cc.enable = 1;
    cmd.cc.reserved = 0;
    cmd.cc.bus = bus;
    cmd.cc.dev = dev;
    cmd.cc.func = func;
    cmd.cc.reg = reg & 0xFC;

    base = 0xcfc + (reg & 0x03);
    outportd(0xCF8, cmd.ci);
    outw(base, data);
}

// writes a config byte
void pciWriteConfigByte(int bus, int dev, int func, int reg, uint8_t data)
{
    uint16_t base;
    union cmd cmd;

	cmd.ci = 0;
	cmd.cc.enable = 1;
    cmd.cc.reserved = 0;
    cmd.cc.bus = bus;
    cmd.cc.dev = dev;
    cmd.cc.func = func;
    cmd.cc.reg = reg & 0xFC;

    base = 0xcfc + (reg & 0x03);
    outportd(0xCF8, cmd.ci);
    outb(base, data);
}

// reads the irq
static void pciReadIrq(pciDev_t *p)
{
	uint8_t irq;
	
	irq = pciReadConfigByte(p->bus, p->dev, p->func, PCI_IRQ_PIN);

	if(irq)
		irq = pciReadConfigByte(p->bus, p->dev, p->func, PCI_IRQ_LINE);

	p->irq = irq;
}

// gets the pci size
uint32_t pciSize(uint32_t base, uint32_t mask)
{
	uint32_t size = mask & base;
	size = size & ~(size-1);
	return(size-1);
}

// reads the pci bases
static void pciReadBases(pciDev_t *p, int count, int rom)
{
	int i;
	uint32_t l, sz, reg;

	memset(p->base, 0, sizeof(p->base));
	memset(p->size, 0, sizeof(p->size));
	memset(p->type, 0, sizeof(p->type));

	for(i=0; i<count; i++)
	{
		reg = PCI_BASE_ADDRESS_0 + (i << 2);
		l = pciReadConfigDword(p->bus, p->dev, p->func, reg);
		pciWriteConfigDword(p->bus, p->dev, p->func, reg, ~0);

		sz = pciReadConfigDword(p->bus, p->dev, p->func, reg);
		pciWriteConfigDword(p->bus, p->dev, p->func, reg, l);

		if(!sz || sz == 0xffffffff)
			continue;
		if(l == 0xffffffff)
			l = 0;

		if((l & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY)
		{
			p->base[i] = l & PCI_BASE_ADDRESS_MEM_MASK;
			p->size[i] = pciSize(sz, PCI_BASE_ADDRESS_MEM_MASK);
			p->type[i] = PCI_IO_RESOURCE_MEM;
		}
		else
		{
			p->base[i] = l & PCI_BASE_ADDRESS_IO_MASK;
			p->size[i] = pciSize(sz, PCI_BASE_ADDRESS_IO_MASK);
			p->type[i] = PCI_IO_RESOURCE_IO;
		}
	}
	
	if(rom)
	{
		p->rom_base = 0;
		p->rom_size = 0;
	
		l = pciReadConfigDword(p->bus, p->dev, p->func, rom);
		pciWriteConfigDword(p->bus, p->dev, p->func, 
							rom, ~PCI_ROM_ADDRESS_ENABLE);
		sz = pciReadConfigDword(p->bus, p->dev, p->func, rom);
		pciWriteConfigDword(p->bus, p->dev, p->func, rom, l);
		
		if (sz && sz != 0xFFFFFFFF)
		{
			p->rom_base = l & PCI_ROM_ADDRESS_MASK;
			sz = pciSize(sz, PCI_ROM_ADDRESS_MASK);
			p->rom_size = p->rom_size + (unsigned long)sz;
		}
	}
}

// probes the PCI
uint16_t pciProbe(int bus, int dev, int func, pciDev_t *pciDev)
{
	int i;
	uint32_t *temp = (uint32_t *)pciDev;
	
	for(i=0; i<4; i++)
		temp[i] = pciReadConfigDword(bus, dev, func, i<<2);

	if(pciDev->vendor_id == 0xffff || pciDev->vendor_id == 0x0)
		return 0;
	
	
	pciDev->bus = bus;
	pciDev->func = func;
	pciDev->dev = dev;
	
	pciDev->current_state = 4;

	switch(pciDev->header_type)
	{
		case PCI_HEADER_TYPE_NORMAL:
	//		printf("%s - %s %x:%x\n", 
	//				pciGetVendorStr(pciDev->vendor_id), 
	//				pciGetDevStr(pciDev->vendor_id, pciDev->device_id),
	//				pciDev->vendor_id, pciDev->device_id);
			pciReadIrq(pciDev);
			pciReadBases(pciDev, 6, PCI_ROM_ADDRESS);
			pciDev->subsys_vendor = 
					pciReadConfigWord(bus, dev, func, PCI_SUBSYSTEM_VENDOR_ID);
			pciDev->subsys_device = 
					pciReadConfigWord(bus, dev, func, PCI_SUBSYSTEM_ID);
/*			if( pciDev->irq != 0 )
				printf("    irq %d, iobase 0x%x\n", pciDev->irq, pciDev->base[0] & ~3);*/
			break;
		case PCI_HEADER_TYPE_BRIDGE:
			pciReadBases(pciDev, 2, PCI_ROM_ADDRESS_1);
	//		printf("%s - %s %x:%x\n", 
	//				pciGetVendorStr(pciDev->vendor_id), 
	//				pciGetDevStr(pciDev->vendor_id, pciDev->device_id),
	//				pciDev->vendor_id, pciDev->device_id);
			break;
		case PCI_HEADER_TYPE_CARDBUS:
			pciReadIrq(pciDev);
			pciReadBases(pciDev, 1, 0);
            pciDev->subsys_vendor = pciReadConfigWord(bus, dev, func, PCI_SUBSYSTEM_VENDOR_ID);
            pciDev->subsys_device = pciReadConfigWord(bus, dev, func, PCI_SUBSYSTEM_ID);
	//	printf("%s - %s %x:%x\n", 
	//				pciGetVendorStr(pciDev->vendor_id),
	//				pciGetDevStr(pciDev->vendor_id, pciDev->device_id),
	//				pciDev->vendor_id, pciDev->device_id);
			break;
		default:
			break;
	}

	return 1;
}

// initializes PCI access
void InitPCI()
{
	// variables for use later
	biosHeader_t *bios32;
	union bios32_t *bios32_s;
	uint32_t pciBiosEntry;
	uint32_t signature, eax, ebx, ecx;
	uint8_t ah, al, bh, bl, cl;

	// bus, device, function
	int bus, dev, func;
	
	// pci device
	pciDev_t pciDev;

	// get the service directory
	bios32 = findBios32ServiceDirectory();
	
	if(bios32 == (biosHeader_t*)NULL)
		return;

	// query it
	bios32_s = (union bios32_t *)bios32;
	if( (pciBiosEntry = bios32Query(bios32_s, 0x049435024)) )
	{		
		// indirect pci addy
		pciIndirect.address = pciBiosEntry;
		
		// get the information
		asm volatile (
							"pushf\n\t"
							"lcall *(%%edi); cld\n\t"
							"jc 1f\n\t"
							"xor %%ah, %%ah\n\t"
							"1:\n\t"
							"popf\n\t"
							: "=d" (signature),
							  "=a" (eax),
							  "=b" (ebx),
							  "=c" (ecx)
							: "1" (PCIBIOS_BIOS_PRESENT),
							  "D" (&pciIndirect)
							: "memory");
		
		// modify and check
		ah = eax >> 8 & 0xff;
		al = eax & 0xff;
		bh = ebx >> 8 & 0xff;
		bl = ebx & 0xff;
		cl = ecx & 0xff;
		if(ah || signature != 0x20494350) // signature == "$PCI"
		{
			// oops!
			printf("PCI: BIOS Not Working\n");
			return;
		}
	
		// go through each bus, device and function
		for(bus=0; bus<4; bus++)
		{
			for(dev=0; dev<32; dev++)
			{
				for(func=0; func<8; func++)
				{
					// probe the pci for this device
					if(pciProbe(bus, dev, func, &pciDev))
					{
						// check for validity
						if(func && !(pciDev.header_type & 0x80))
							continue;
							
						// check first that it's not the same
/*						if( pciCount > 0 )
						{
							// if the previous device is the same, get out
							if( pciDevices[pciCount-1]->vendor_id == pciDev.vendor_id
														&& 
								pciDevices[pciCount-1]->device_id == pciDev.device_id
								)
							{
								continue;
							}
						}*/
						
						// print it
						printf( "%s - %s %x:%x\n", 
							pciGetVendorStr( pciDev.vendor_id ), 
							pciGetDevStr( pciDev.vendor_id, pciDev.device_id ),
							pciDev.vendor_id, pciDev.device_id );
						printf( "\tirq: %d, iobase 0x%x\n", pciDev.irq, pciDev.base[0] & ~3 );
					
						
								// create space
						pciDevices[pciCount] = (pciDev_t *) kmalloc( sizeof( pciDev_t ) );
						
						// copy the data
						memcpy( pciDevices[pciCount], &pciDev, sizeof( pciDev_t ) );
								
						// increment the count
						pciCount++;
					}
				}
			}
		}
		
		// tell me
		printf("PCI: BIOS Version %x.%x Present, ", bh, bl);
		printf("%d devices attached.\n", pciCount);						
	}
}
